package org.gpp.proj1.demo.terrain;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.FloatBuffer;
import java.util.Scanner;

import org.gpp.proj1.util.GeometryBatch;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.FirstPersonHandler;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;

public class LevelMeshBuilder extends SimpleGame {

	private static final int BLOCK_SIZE = 32;

	private String map;

	public LevelMeshBuilder( String map ) {
		this.map = map;
	}

	@Override
	protected void simpleInitGame() {
		
		this.cam.setLocation(new Vector3f(50, 100, 50));
		this.cam.lookAt(new Vector3f(262,21,-219), new Vector3f(0,1,0));
	    /**
	     * Construindo o cenário
	     */
		try {
			readLevel( map, this.rootNode );
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		/**
		 * Desabilitando iluminação e criando neblina.
		 * 
		 * O "setFrustumFar" é usado para definir até
		 * aonde a câmera deve mostrar objetos. Note que
		 * a distância é igual ao do final da neblina, para
		 * evitar renderização desnecessária
		 */
		this.lightState.setEnabled( false );
		
		/**
		 * Deixando o movimento mais rápido =)
		 */
		FirstPersonHandler handler = (FirstPersonHandler) this.input;
		handler.getKeyboardLookHandler().setActionSpeed( 200.0f );
	}

	public static void readLevel( String filename, Node root ) throws FileNotFoundException {
		Scanner scanner = new Scanner( new File( filename ) );
				
		int width = scanner.nextInt();
		int height = scanner.nextInt();
		
		int macroHeight = height * BLOCK_SIZE;
		
		int[][] levelHeightMap = new int[width + 2][height + 2];
		
		/**
		 * A primeira e segunda passagens são para inicializar e
		 * preencher o mapa de alturas
		 */
		for( int y = 0; y < height + 2; y++ ) {
			for( int x = 0; x < width + 2; x++ ) {
				levelHeightMap[x][y] = 0;
			}
		}
		
		for( int y = 0; y < height; y++ ) {
			for( int x = 0; x < width; x++ ) {
				levelHeightMap[x + 1][y + 1] = scanner.nextInt();
			}
		}

		 TextureState terrainSides, terrainTop;
		
		/**
		 * Carregando a textura para o lado dos blocos.
		 * 
		 * O "setWrap" é para fazer com que ela se repita ao ser renderizada,
		 * e não redimensionada como é feito normalmente.
		 */
		Texture terrainSideTexture = TextureManager.loadTexture( "test/terrainDemo/rocks.jpg", Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
		terrainSideTexture.setWrap( Texture.WM_WRAP_S_WRAP_T );
	    terrainSides = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
	    terrainSides.setEnabled(true);
	    terrainSides.setTexture( terrainSideTexture );
	    
		/**
		 * Carregando a textura para o topo dos blocos
		 */
	    Texture terrainTopTexture = TextureManager.loadTexture( "test/terrainDemo/GrassSample.jpg", Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
	    terrainTopTexture.setWrap( Texture.WM_WRAP_S_WRAP_T );
	    terrainTop = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
	    terrainTop.setEnabled(true);	    
	    terrainTop.setTexture( terrainTopTexture );
		
		Quaternion quat = new Quaternion();	
		quat.fromAngleAxis( - FastMath.PI / 2, new Vector3f( 1, 0, 0 ) );
		quat.normalize();
		
		GeometryBatch topBatch = null;
		GeometryBatch northBatch = null;
		GeometryBatch southBatch = null;
		GeometryBatch eastBatch = null;
		GeometryBatch westBatch = null;
		
		
		topBatch = new GeometryBatch( "Top", new Box("Base", new Vector3f(0,0,0), new Vector3f(0,0,0) ) );
		topBatch.setRenderState( terrainTop );					
		topBatch.updateRenderState();
		topBatch.setLocalRotation( quat );
				
		northBatch = new GeometryBatch( "North", new Box("Base", new Vector3f(0,0,0), new Vector3f(0,0,0) ) );
		northBatch.setRenderState( terrainSides );					
		northBatch.updateRenderState();
		
		southBatch = new GeometryBatch( "South", new Box("Base", new Vector3f(0,0,0), new Vector3f(0,0,0) ) );
		southBatch.setRenderState( terrainSides );					
		southBatch.updateRenderState();
		
		quat = new Quaternion();
		quat.fromAngleAxis( - FastMath.PI / 2, new Vector3f( 0, 1, 0 ) );
		quat.normalize();
		
		eastBatch = new GeometryBatch( "East", new Box("Base", new Vector3f(0,0,0), new Vector3f(0,0,0) ) );
		eastBatch.setRenderState( terrainSides );					
		eastBatch.updateRenderState();
		eastBatch.setLocalRotation( quat );
		
		quat = new Quaternion();
		quat.fromAngleAxis( - FastMath.PI / 2, new Vector3f( 0, 1, 0 ) );
		quat.normalize();
		
		westBatch = new GeometryBatch( "West", new Box("Base", new Vector3f(0,0,0), new Vector3f(0,0,0) ) );
		westBatch.setRenderState( terrainSides );					
		westBatch.updateRenderState();
		westBatch.setLocalRotation( quat );
		
		for( int y = 0; y < height; y++ ) {
			for( int x = 0; x < width; x++ ) {
				int z = levelHeightMap[x + 1][y + 1];

				/** West */
				if( z > levelHeightMap[x][y + 1] ) {
					int h = z - levelHeightMap[x][y + 1];
					
					Quad west = new Quad( "west" + x + y, BLOCK_SIZE, BLOCK_SIZE / 2 * h );
					west.setModelBound( new BoundingBox() );
					west.updateModelBound();

					FloatBuffer buffer = west.getTextureBuffer();
					buffer.clear();
					buffer.put(0).put(h);
					buffer.put(0).put(0);
					buffer.put(1).put(0);
					buffer.put(1).put(h);
					buffer.rewind();

					westBatch.addGeometry( west, new Vector3f( y * BLOCK_SIZE - macroHeight, z * BLOCK_SIZE / 2 - BLOCK_SIZE / 4 * h, - x * BLOCK_SIZE + BLOCK_SIZE / 2 ) );
				}
				
				if( z > levelHeightMap[x + 2][y + 1] ) {
					int h = z - levelHeightMap[x + 2][y + 1];
					
					Quad east = new Quad( "east" + x + y, BLOCK_SIZE, BLOCK_SIZE / 2 * h );
					east.setLocalTranslation( new Vector3f( x * BLOCK_SIZE + BLOCK_SIZE / 2, z * BLOCK_SIZE / 2 - BLOCK_SIZE / 4 * h, y * BLOCK_SIZE ) );
					east.setLocalRotation( quat );
					
					east.setModelBound( new BoundingBox() );
					east.updateModelBound();
					
					FloatBuffer buffer = east.getTextureBuffer();
					buffer.clear();
					buffer.put(0).put(h);
					buffer.put(0).put(0);
					buffer.put(1).put(0);
					buffer.put(1).put(h);
					buffer.rewind();					
					
					east.setRenderState( terrainSides );
					east.updateRenderState();
				
					eastBatch.addGeometry( east, new Vector3f( y * BLOCK_SIZE - macroHeight, z * BLOCK_SIZE / 2 - BLOCK_SIZE / 4 * h, - x * BLOCK_SIZE - BLOCK_SIZE / 2 ) );
				}
				
				if( z > levelHeightMap[x + 1][y] ) {
					int h = z - levelHeightMap[x + 1][y];
					
					Quad north = new Quad( "north" + x + y, BLOCK_SIZE, BLOCK_SIZE / 2 * h );
					
					FloatBuffer buffer = north.getTextureBuffer();
					buffer.clear();
					buffer.put(0).put(h);
					buffer.put(0).put(0);
					buffer.put(1).put(0);
					buffer.put(1).put(h);
					buffer.rewind();
					
					northBatch.addGeometry( north, new Vector3f( x * BLOCK_SIZE, z * BLOCK_SIZE / 2 - BLOCK_SIZE / 4 * h, y * BLOCK_SIZE - BLOCK_SIZE / 2 - macroHeight ) );
				}
				
				if( z > levelHeightMap[x + 1][y + 2] ) {
					int h = z - levelHeightMap[x + 1][y + 2];					
					
					Quad south = new Quad( "south" + x + y, BLOCK_SIZE, BLOCK_SIZE / 2 * h );
					
					FloatBuffer buffer = south.getTextureBuffer();
					buffer.clear();
					buffer.put(0).put(h);
					buffer.put(0).put(0);
					buffer.put(1).put(0);
					buffer.put(1).put(h);
					buffer.rewind();
										
					southBatch.addGeometry( south, new Vector3f( x * BLOCK_SIZE, z * BLOCK_SIZE / 2 - BLOCK_SIZE / 4 * h, y * BLOCK_SIZE + BLOCK_SIZE / 2 - macroHeight ) );
				}
			}
		}
		
		for( int x = 0; x < width; x++ ) {
			for( int y = 0; y < height; y++ ) {
				Quad top = new Quad( "top" + x + y, BLOCK_SIZE, BLOCK_SIZE );
					
				FloatBuffer buffer = top.getTextureBuffer();
				buffer.clear();
				buffer.put(0).put(1);
				buffer.put(0).put(0);
				buffer.put(1).put(0);
				buffer.put(1).put(1);
				buffer.rewind();
					
				topBatch.addGeometry( top, new Vector3f( x * BLOCK_SIZE, macroHeight - y * BLOCK_SIZE, levelHeightMap[x + 1][y + 1] * BLOCK_SIZE / 2 ) );
			}
		}
		
		root.attachChild( topBatch );
		root.attachChild( northBatch );
		root.attachChild( southBatch );
		root.attachChild( westBatch );
		root.attachChild( eastBatch );
	}
	
	
	public static void main(String[] args) {
		LevelMeshBuilder lev = new LevelMeshBuilder( args[0] );
		lev.start();
	}
}
